package camid.services.cloudmanager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import camid.distribution.activeobject.ProxyBuilder;
import camid.distribution.broker.Requestor;
import camid.distribution.protocol.Endpoint;
import camid.main.ClientFacade;
import camid.services.nameservice.INameService;
import camid.services.nodemanager.INodeManager;
import camid.services.nodemanager.NodeManager;
import camid.util.monitoring.NodeEvaluator;
import camid.util.monitoring.NodeMetrics;

public class ClusterManager {

	private INameService ns;
	private Requestor requestor;
	
	private Map<String, Double> costMap;
	private NodeEvaluator nodeEvaluator;
	
	private CloudManagerAgent agent;
	private Thread agentThread;
	
	private static final Logger logger = LogManager.getLogger(ClusterManager.class.getName());
	
	public static final Long BALANCING_INTERVAL = 30000L;
	
	public static final Double NUMBER_OBJECTS_WEIGHT = 0.1;
	
	public static final Double THROUGHPUT_OBJECTS_WEIGHT = 0.1;
	
	public static final Double RESPONSE_TIME_WEIGHT = 0.40;
	
	public static final Double CPU_WEIGHT = 0.30;
	
	public static final Double MEM_WEIGHT = 0.30;
	
	public ClusterManager(INameService ns)
	{
		this.ns = ns;
		this.costMap = Collections.synchronizedMap(new HashMap<String, Double> ());
		this.nodeEvaluator = new NodeEvaluator();
		this.nodeEvaluator.setCPUWeight(CPU_WEIGHT);
		this.nodeEvaluator.setMemWeight(MEM_WEIGHT);
		this.nodeEvaluator.setResponseTimeWeight(RESPONSE_TIME_WEIGHT);
		this.nodeEvaluator.setNumberOfObjectsWeight(NUMBER_OBJECTS_WEIGHT);
	}
	
	public void startAgent()
	{
		this.agent = new CloudManagerAgent(this, BALANCING_INTERVAL);
		this.agentThread = new Thread(agent);
		this.agentThread.start();
	}

	public void setRequestor(Requestor requestor) 
	{
		this.requestor = requestor;	
	}

	public Integer getObjectCount(String nodeId) 
	{
		INodeManager nm = getNodeManagerProxy(nodeId);
		Integer count = nm.getNumberOfObjects();
		return count;
	}

	public Integer getTotalObjectCount() {
		Integer result = 0;
		List<String> nodes = this.ns.getAllNodes();
		for(String nodeId : nodes)
		{	
			try
			{
				Integer count = getObjectCount(nodeId);
				result += count;
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
			
		}
		return result;
	}
	
	//TODO
	public Double getObjectCost(String nodeId) 
	{
		INodeManager nm = getNodeManagerProxy(nodeId);
		Double cost = nm.getNodeCost();
		return cost;
	}
	
	public NodeMetrics getNodeMetrics(String nodeId) 
	{
		INodeManager nm = getNodeManagerProxy(nodeId);
		NodeMetrics metrics = nm.getNodeMetrics();
		return metrics;
	}
	
	private INodeManager getNodeManagerProxy(String nodeId)
	{
		Endpoint ep = this.ns.getNodeEndpoint(nodeId);
		INodeManager nm = ClientFacade.getInstance()
				.lookupObject(INodeManager.class, NodeManager.class, NodeManager.NODE_MANAGER_OBJECTID, ep);
		return nm;
	}

	public void updateCostMap()
	{
		List<String> nodes = this.ns.getAllNodes();
		List<NodeMetrics> nodeMetricList = new ArrayList<NodeMetrics> (nodes.size());
		double cost = 0.0;
		for(String nodeId : nodes)
		{
			NodeMetrics metrics = getNodeMetrics(nodeId);
			cost = calculateCost(metrics);
			nodeMetricList.add(metrics);
		}
		
		for(int i = 0; i < nodes.size(); i++)
		{
			cost = calculateCost(nodeMetricList.get(i));
			this.costMap.put(nodes.get(i), cost);
		}
	}
	
	
	
	private Double calculateCost(NodeMetrics metrics) 
	{
		double cost = this.nodeEvaluator.feedMetric(metrics);
		return cost;
	}

	public Map<String, Double> getCostMap() 
	{
		return this.costMap;
	}

	public void balanceNodes(String nodeId, String nodeId2) {
		
		Double cost1 = costMap.get(nodeId);
		Double cost2 = costMap.get(nodeId2);
		
		cost1 = cost1 == null ? 0.0 : cost1;
		cost2 = cost2 == null ? 0.0 : cost2;
		
		double meanCost = ( cost1 + cost2 ) / 2;
		if(cost1 > cost2)
		{
			double migrationCost = Math.abs(cost1 - meanCost);
			if(migrationCost > 0)
			{
				Double quantity = nodeEvaluator.numberOfObjectsFromCost(migrationCost);
				logger.info("Ammount of objects to migrate: " + quantity);
				if(quantity.intValue() > 0)
				{
					INodeManager nm = getNodeManagerProxy(nodeId);
					nm.migrateObjects(nodeId2, quantity.intValue());
				}
			}
		}
		else if(cost2 > cost1)
		{
			double migrationCost = cost2 - meanCost;
			if(migrationCost > 0)
			{
				Double quantity = nodeEvaluator.numberOfObjectsFromCost(migrationCost);
				logger.info("Ammount of objects to migrate: " + quantity);
				if(quantity.intValue() > 0)
				{
					INodeManager nm = getNodeManagerProxy(nodeId2);
					nm.migrateObjects(nodeId, quantity.intValue());
				}
			}
		}
		
		
	}

	public void sendAlert(String nodeId, int redirectCount) {
		INodeManager nodeManager = getNodeManagerProxy(nodeId);
		nodeManager.alert(redirectCount);
	}

	public void shutdown()
	{
		if(this.agent != null)
		{
			this.agent.stop();
		}
	}

	

}
